/**
 *  Copyright 2012 Rafal Korytkowski
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.googlecode.rapidjunit.ui.views;

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.ViewPart;

import com.googlecode.rapidjunit.RapidJUnit;
import com.googlecode.rapidjunit.finder.JUnitEngine;

/**
 * The View.
 * 
 * @author Rafal Korytkowski
 */
public class RapidJUnitView extends ViewPart {

	/**
	 * The ID of the view as specified by the extension.
	 */
	public static final String ID = "com.googlecode.rapidjunit.ui.views.RapidJUnitView";

	private TreeViewer viewer;

	class TreeObject implements IAdaptable {
		private IType type;
		private TreeParent parent;

		public TreeObject(IType type) {
			this.type = type;
		}

		public String getName() {
			return (type != null) ? type.getElementName() : "";
		}

		public void setParent(TreeParent parent) {
			this.parent = parent;
		}

		public TreeParent getParent() {
			return parent;
		}

		public String toString() {
			return getName();
		}

		public Object getAdapter(@SuppressWarnings("rawtypes") Class key) {
			if (key.isInstance(type)) {
				return type;
			}
			return null;
		}
	}

	class TreeParent extends TreeObject {
		private List<TreeObject> children;

		public TreeParent(IType clazz) {
			super(clazz);
			children = new ArrayList<TreeObject>();
		}

		public void addChild(TreeObject child) {
			children.add(child);
			child.setParent(this);
		}

		public void removeChild(TreeObject child) {
			children.remove(child);
			child.setParent(null);
		}

		public TreeObject[] getChildren() {
			return (TreeObject[]) children.toArray(new TreeObject[children
					.size()]);
		}

		public boolean hasChildren() {
			return children.size() > 0;
		}
	}

	class ViewContentProvider implements IStructuredContentProvider,
			ITreeContentProvider, Observer {
		private TreeParent invisibleRoot;
		private Viewer viewer;
		
		public ViewContentProvider(Viewer viewer) {
			this.viewer = viewer;
		}

		public void inputChanged(Viewer v, Object oldInput, Object newInput) {
		}

		public void dispose() {
		}

		public Object[] getElements(Object parent) {
			if (parent.equals(getViewSite())) {
				if (invisibleRoot == null)
					initialize();
				return getChildren(invisibleRoot);
			}
			return getChildren(parent);
		}

		public Object getParent(Object child) {
			if (child instanceof TreeObject) {
				return ((TreeObject) child).getParent();
			}
			return null;
		}

		public Object[] getChildren(Object parent) {
			if (parent instanceof TreeParent) {
				return ((TreeParent) parent).getChildren();
			}
			return new Object[0];
		}

		public boolean hasChildren(Object parent) {
			if (parent instanceof TreeParent)
				return ((TreeParent) parent).hasChildren();
			return false;
		}

		private void initialize() {
			invisibleRoot = new TreeParent(null);
			for (IType tested : RapidJUnit.getManager().getTestedClasses()) {
				TreeParent parent = new TreeParent(tested);
				for (IType test : RapidJUnit.getManager().getTestCases(tested)) {
					TreeObject child = new TreeObject(test);
					parent.addChild(child);
				}
				invisibleRoot.addChild(parent);
			}
		}

		@Override
		public void update(Observable o, Object arg) {
			initialize();
			viewer.refresh();
		}
	}

	class ViewLabelProvider extends LabelProvider {

		public String getText(Object obj) {
			return obj.toString();
		}

		public Image getImage(Object obj) {
			String imageKey = ISharedImages.IMG_OBJ_FILE;
			if (obj instanceof TreeParent)
				imageKey = ISharedImages.IMG_OBJ_FILE;
			return PlatformUI.getWorkbench().getSharedImages()
					.getImage(imageKey);
		}
	}

	class NameSorter extends ViewerSorter {
	}
	
	class RemoveAction extends Action {
		private TreeViewer viewer;
		
		public RemoveAction(TreeViewer viewer) {
			super();
			this.viewer = viewer;
		}
		
		@Override
		public void run() {
			if (!(viewer.getSelection() instanceof IStructuredSelection)) {
		         return;
			}
			IStructuredSelection structuredSelection = (IStructuredSelection) viewer.getSelection();

			Object[] elements = structuredSelection.toArray();
			for (Object element : elements) {
				if (!(element instanceof TreeObject) && element instanceof IAdaptable) {
					element = ((IAdaptable) element).getAdapter(TreeObject.class);
				}
				if (element instanceof TreeParent) {
					IType testedClass = (IType) ((TreeParent) element).getAdapter(IType.class);
					RapidJUnit.getManager().removeTestedClass(testedClass);
				} else if (element instanceof TreeObject) {
					IType testCase = (IType) ((TreeObject) element).getAdapter(IType.class);
					IType testedClass = (IType) ((TreeObject) element).getParent().getAdapter(IType.class);
					RapidJUnit.getManager().removeTestCase(testedClass, testCase);
				}
			}
		}
	}

	class ViewDropTarget extends DropTargetAdapter {

		public ViewDropTarget(RapidJUnitView view, TreeViewer viewer) {
			DropTarget target = new DropTarget(viewer.getControl(),
					DND.DROP_MOVE | DND.DROP_COPY);
			target.setTransfer(new Transfer[] { LocalSelectionTransfer
					.getTransfer() });
			target.addDropListener(this);
		}

		@Override
		public void dragEnter(DropTargetEvent event) {
			if (event.detail == DND.DROP_MOVE
					|| event.detail == DND.DROP_DEFAULT) {
				if ((event.operations & DND.DROP_COPY) != 0) {
					event.detail = DND.DROP_COPY;
				} else {
					event.detail = DND.DROP_NONE;
				}
			}
		}

		@Override
		public void drop(DropTargetEvent event) {
			if (!(event.data instanceof IStructuredSelection)) {
				return;
			}

			TreeParent parent = null;
			if (event.item != null) {
				if (event.item.getData() instanceof TreeParent) {
					parent = (TreeParent) event.item.getData();
				}
			}

			IStructuredSelection structuredSelection = (IStructuredSelection) event.data;
			Object[] elements = structuredSelection.toArray();
			for (Object element : elements) {
				if (!(element instanceof IJavaElement)
						&& element instanceof IAdaptable) {
					element = ((IAdaptable) element)
							.getAdapter(IJavaElement.class);
				}
				if (element instanceof ICompilationUnit) {
					ICompilationUnit cu = (ICompilationUnit) element;
					IType type = cu.findPrimaryType();
					boolean isJUnit = JUnitEngine.isJUnit(type);
					if (parent != null && isJUnit) {
						IType parentType = (IType) parent
								.getAdapter(IType.class);
						RapidJUnit.getManager().addTestCase(parentType, type);
					} else if (parent == null && !isJUnit) {
						RapidJUnit.getManager().addTestedClass(type);
					}
				}
			}
		}

	}

	/**
	 * The constructor.
	 */
	public RapidJUnitView() {
	}

	/**
	 * This is a callback that will allow us to create the viewer and initialize
	 * it.
	 */
	@Override
	public void createPartControl(Composite parent) {
		viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		ViewContentProvider contentProvider = new ViewContentProvider(viewer);
		RapidJUnit.getManager().addObserver(contentProvider);
		viewer.setContentProvider(contentProvider);
		viewer.setLabelProvider(new ViewLabelProvider());
		viewer.setSorter(new NameSorter());
		viewer.setInput(getViewSite());
		hookGlobalActions();
		hookDragAndDrop();
	}
	
	@Override
	public void dispose() {
		RapidJUnit.getManager().deleteObservers();
		super.dispose();
	}

	private void hookGlobalActions() {
		getViewSite().getActionBars().setGlobalActionHandler(
				ActionFactory.DELETE.getId(), new RemoveAction(viewer));
	}

	private void hookDragAndDrop() {
		new ViewDropTarget(this, viewer);
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	@Override
	public void setFocus() {
		viewer.getControl().setFocus();
	}
}